package xin.alum.aim;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.PooledByteBufAllocator;
import io.netty.channel.*;
import io.netty.channel.epoll.Epoll;
import io.netty.channel.epoll.EpollEventLoopGroup;
import io.netty.channel.epoll.EpollServerSocketChannel;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
import io.netty.handler.ssl.util.SelfSignedCertificate;
import io.netty.handler.timeout.IdleStateHandler;
import io.netty.handler.timeout.WriteTimeoutHandler;
import io.netty.handler.traffic.ChannelTrafficShapingHandler;
import io.netty.util.ResourceLeakDetector;
import io.netty.util.internal.logging.InternalLogger;
import io.netty.util.internal.logging.InternalLoggerFactory;
import lombok.experimental.UtilityClass;
import xin.alum.aim.config.AimConfig;
import xin.alum.aim.constant.AimConstant;
import xin.alum.aim.groups.ClusterMessage;
import xin.alum.aim.groups.ClusterProperties;
import xin.alum.aim.handler.AimReceive;
import xin.alum.aim.handler.AimReceiver;
import xin.alum.aim.handler.LoggerHandler;
import xin.alum.aim.server.PortUnificationHandshake;
import xin.alum.aim.util.ApplicationContextUtil;

import javax.net.ssl.SSLException;
import java.security.cert.CertificateException;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * @author alum(alum @ live.cn)
 * @date 2021/8/4 18:32
 */
@UtilityClass
public class Aim {

    /**
     * aim 业务处理
     */
    public AimReceive request = new AimReceiver();
    /**
     * 集群消息处理
     */
    public ClusterMessage message;
    /**
     * 配置属性
     */
    public AimConfig properties = new AimConfig();
    /**
     * 日志输出
     */
    private final InternalLogger logger = InternalLoggerFactory.getInstance(Aim.class);

    /**
     * 默认启动AIM服务
     */
    public void start() {
        properties = ApplicationContextUtil.getBean(AimConfig.class);
        if (properties != null) {
            start(properties);
        }
    }

    /**
     * 集群启动
     *
     */
    public void start(ClusterMessage clusterMessage) {
        message = clusterMessage;
        start(Objects.requireNonNull(ApplicationContextUtil.getBean(AimConfig.class)));
    }

    /**
     * 自定义启动属性
     */
    public void start(AimConfig prop) {
        properties = prop;
        if (!prop.isEnable()) {
            logger.warn("{}服务已关闭,如需启用,请AimProperties的enable参数", AimConstant.PROJECT_NAME);
            return;
        }

        AimReceive receiver = ApplicationContextUtil.getBean(AimReceive.class);
        request = receiver != null ? receiver : request;

        if (Objects.requireNonNull(prop.getCluster().getMode()) == ClusterProperties.ClusterMode.None) {
            logger.warn("{} 服务未开启集群模式", AimConstant.PROJECT_NAME);
        } else {//注入集群
            logger.warn("{} 服务开启【{}】集群模式", AimConstant.PROJECT_NAME, prop.getCluster().getMode());
        }
        ResourceLeakDetector.setLevel(ResourceLeakDetector.Level.ADVANCED);
        //解决CPU使用过高
        System.setProperty("org.jboss.netty.epollBugWorkaround", "true");
        final int inetPort = prop.getPort();
        ServerBootstrap serverBootstrap = new ServerBootstrap();
        // 做是否支持epoll轮询判断以获取更高性能
        int k=1024;
        int wHigh = properties.getWrite().getHigh() * k;
        int wLow = properties.getWrite().getLow() * k;
        int rcvBuf = properties.getWrite().getRcvbuf() * k;
        int sendBuf = properties.getWrite().getSndbuf() * k;
        EventLoopGroup boss = Epoll.isAvailable() ? new EpollEventLoopGroup(prop.getThreads().getParent()) : new NioEventLoopGroup(prop.getThreads().getParent());
        EventLoopGroup worker = Epoll.isAvailable() ? new EpollEventLoopGroup(prop.getThreads().getChild()) : new NioEventLoopGroup(prop.getThreads().getChild());
        try {
            serverBootstrap.group(boss, worker).channel(Epoll.isAvailable() ? EpollServerSocketChannel.class : NioServerSocketChannel.class)
                    //三次握手后的等待队列
                    .option(ChannelOption.SO_BACKLOG, AimConstant.TEXT_FRAME_MAX_LENGTH)
                    //连接超时,默认是30s
                    .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 15000)
                    .option(ChannelOption.SO_RCVBUF, rcvBuf)
                    //注意：Netty 4.1.59版本没有MAX_MESSAGES_PER_WRITE参数
                    .option(ChannelOption.MAX_MESSAGES_PER_WRITE, prop.getWrite().getCount())
                    .option(ChannelOption.WRITE_SPIN_COUNT, prop.getWrite().getCount())
                    .option(ChannelOption.SO_REUSEADDR, true)
                    .option(ChannelOption.SINGLE_EVENTEXECUTOR_PER_GROUP, false)
                    .option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws CertificateException, SSLException {
                            //SSL 请求处理
                            if (prop.isSsl()) {
                                SelfSignedCertificate ssc = new SelfSignedCertificate();
                                SslContext sslCtx = SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey()).build();
                                ch.pipeline().addLast(sslCtx.newHandler(ch.alloc()));
                            }
                            ch.pipeline().addLast(new PortUnificationHandshake());
                            //Netty-Debug日志输出
                            if (logger.isDebugEnabled()) {
                                ch.pipeline().addLast(new LoggerHandler(LogLevel.INFO));
                            }
                            //通道限流单位byte, 0无限制
                            ch.pipeline().addLast(new ChannelTrafficShapingHandler(wHigh, wLow, 1000L));
                            //启用写超时
                            if (prop.getWrite().getTimeOut() != 0) {
                                ch.pipeline().addLast(new WriteTimeoutHandler(prop.getIdle().getReader()));
                            }
                            ch.pipeline().addLast(new IdleStateHandler(prop.getIdle().getReader(), prop.getIdle().getWriter(), prop.getIdle().getAll(), TimeUnit.SECONDS));
                        }
                    }).childOption(ChannelOption.WRITE_BUFFER_WATER_MARK, new WriteBufferWaterMark(wLow, wHigh))
                    .childOption(ChannelOption.SO_SNDBUF, sendBuf)
                    .childOption(ChannelOption.TCP_NODELAY, false)
                    .childOption(ChannelOption.SINGLE_EVENTEXECUTOR_PER_GROUP, false)
                    //直接关闭SOCKET不在WRITE
                    .childOption(ChannelOption.SO_LINGER, 0)
                    .childOption(ChannelOption.SO_KEEPALIVE, false);

            //资源泄漏检测
            ResourceLeakDetector.setLevel(ResourceLeakDetector.Level.ADVANCED);
            ChannelFuture future = serverBootstrap.bind(inetPort).sync();
            future.channel().newSucceededFuture().addListener(f -> {
                String logBanner = "{} 服务启动成功,端口号为 {}.";
                logger.info(logBanner, AimConstant.PROJECT_NAME, inetPort);
                logBanner = "{} 通道写入水位{}/{}.";
                logger.info(logBanner, AimConstant.PROJECT_NAME, wLow, wHigh);
                logBanner = "\n" + "* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\n" + "*                                                                                   *\n" + "*                               Welcome to {}.                                     *\n" + "*                                                                                   *\n" + "*            If you have any questions, please contact us alum@live.cn.             *\n" + "*                                                                                   *\n" + "* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\n";
                logger.info(logBanner, AimConstant.PROJECT_NAME);
            });
            //future.channel().closeFuture();//.sync();
        } catch (Exception e) {
            logger.error(e);
        } finally {
            //释放资源,注释掉异步处理
            //boss.shutdownGracefully();
            //worker.shutdownGracefully();
        }
    }
}
